package com.manlitech.cloudboot.config.dubboconfig;

import com.alibaba.fastjson.JSONObject;
import com.manlitech.cloudboot.basebootconfig.annotation.Permissions;
import com.manlitech.cloudboot.common.constant.LogAndAuthConstant;
import com.manlitech.cloudboot.common.enums.ResultEnums;
import com.manlitech.cloudboot.basebootconfig.exception.MyException;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.common.utils.ReflectUtils;
import org.apache.dubbo.rpc.*;
import org.apache.dubbo.rpc.service.GenericService;
import org.slf4j.MDC;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * @author shensg
 * @Description dubbo拦截
 * @Date 2020:09:27 11:47
 */
@Activate(group = "provider" , order = -100)
@Slf4j
public class DubboProviderFilter implements Filter {

    @SneakyThrows
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        /** request 透传 及校验*/
        String token = requestTransport();
        /** 获取rpc方法上的权限 鉴权*/
        authFilter(invoker , invocation , token);
        /** 通过校验发送请求*/
        //开始时间
        long startTime = System.currentTimeMillis();
        //调用结果
        Result result = invoker.invoke(invocation);
        //结束时间
        long elapsedTime = System.currentTimeMillis() - startTime;
        //如果发生错误
        if(result.hasException() && invoker.getInterface() != GenericService.class){
            log.error("提供dubbo服务执行异常=>{}" , result.getException());
        }else{
            log.info("提供的dubbo服务执行成功,服务:{} , 方法:{} , 耗时:{} " ,
                    invocation.getInvoker().getInterface().getName(),
                    invocation.getMethodName(),
                    elapsedTime);
        }
        return result;
    }
    /**
     * 参数透传
     */
    private String requestTransport() {
        String token = null;
        String traceId = null;
        RpcContext context = RpcContext.getContext();
        /**
         * 获取 token 和 traceId
         */
        if (RpcContext.getContext().getRequest() != null && RpcContext.getContext().getRequest() instanceof HttpServletRequest) {
            /**
             * 注意的是只有在设置server="jetty"或server="tomcat"或server="servlet"才能通过以下的方式获取到。
             */
            //强转
            HttpServletRequest request = (HttpServletRequest) context.getRequest();
            //获取token
            token = request.getHeader(LogAndAuthConstant.TOKEN);
            //获取traceid
            traceId = request.getHeader(LogAndAuthConstant.TRACEID);
        }else{
            String attachmentMapStr = context.getAttachment("attachmentMapStr");
            Map<String,Object> attachmentMap = (Map)JSONObject.parse(attachmentMapStr);
            if(attachmentMap.size() > 0){
                traceId = (String) attachmentMap.get(LogAndAuthConstant.TRACEID);
                token = (String) attachmentMap.get(LogAndAuthConstant.TOKEN);
            }
        }
        //设置traceid
        MDC.put(LogAndAuthConstant.TRACEID , traceId);
        return token;
    }

    /**
     * 权限校验
     */
    private boolean authFilter(Invoker<?> invoker , Invocation invocation , String token) throws NoSuchMethodException, ClassNotFoundException {
        // 获取方法注解
        //接口名称
        Class<?> serviceType = invoker.getInterface();
        //接口方法名称
        String methodName = invocation.getMethodName();
        Method method = ReflectUtils.findMethodByMethodName(serviceType, methodName);
        Permissions needVerify = method.getAnnotation(Permissions.class);
        if (needVerify == null){
            // 没有权限注解的
            /**
             * 排除dubbo启动调用 com.alibaba.cloud.dubbo.service.DubboMetadataService
             * org.apache.dubbo.rpc.service.GenericService , 方法:$invoke
             * 等同 invoker.getInterface() == GenericService.class
             */
            log.warn("方法: {} 没有Permissions 注解 "  , method.getClass().getName()+"."+method.getName());
        } else if(needVerify.typeOfAccess() == Permissions.METHOD_LOGIN_PERMISSONS) {
            // 需要权限
            //校验登陆
            authLogin(token);
        } else if(needVerify.typeOfAccess() == Permissions.METHOD_LOGIN) {
            // 需要登陆
            authLogin(token);
        } else if(needVerify.typeOfAccess() == Permissions.METHOD_OPEN){
            // 开放接口
        }else{
            // 没有权限注解的
            log.error("方法: {} Permissions.typeOfAccess  权限不明确 "  , method.getClass().getName()+"."+method.getName());
        }

        return true;
    }

    /**
     * 校验token
     * @param token
     */
    private void authLogin(String token) {
        if(StringUtils.isBlank(token)){
            throw new MyException(ResultEnums.ERROR_TOKEN);
        }
    }

}
